<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/SDWebImage3/">
<meta property="og:title" content="       SDWebImage Source Probe: Downloader | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       SDWebImage Source Probe: Downloader | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/SDWebImage3/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>SDWebImage Source Probe: Downloader</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2016-10-04"> 2016-10-04
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>为了进行图片下载操作，通过 <code>SDWebImageManager</code> 这座桥梁，有效控制了图片下载的时机和同缓存的协同操作。这篇来关注一下在 SD 中，Downloader Class 的具体实现。</p>
<h2 id="downloader-">Downloader 中的一些枚举</h2>
<p>在 <code>SDWebImageDownloader.m</code> 中，可以发现这么一个属性：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@property</span> <span class="p">(</span><span class="n">strong</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">NSOperationQueue</span> <span class="o">*</span><span class="n">downloadQueue</span><span class="p">;</span></code></pre></div>
<p><code>NSOperation</code> 表示一个独立的控制单元，也就是我们所说的线程。而 <code>NSOperationQueue</code> 控制着这些并行操作的执行，以队列的数据结构特点，从而实现线程优先级的控制。而在 <code>SDWebImage</code> 中，很显然是用来管理 <code>SDWebImageDownloaderOperation</code> 。对于 <code>SDWebImageDownloaderOperation</code> 后面将会单独放在一篇博文中介绍。</p>
<p>同 Manager 一样，我们先来看看在 <code>.h</code> 文件中所有的下载模式枚举。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">typedef</span> <span class="no">NS_OPTIONS</span><span class="p">(</span><span class="no">NSUInteger</span><span class="p">,</span> <span class="no">SDWebImageDownloaderOptions</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">低优先级（常用）</span>
    <span class="no">SDWebImageDownloaderLowPriority</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">显示下载进程</span>
    <span class="no">SDWebImageDownloaderProgressiveDownload</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">默认情况下是不需要</span> <span class="no">NSURLCache</span> <span class="err">的。</span>
    <span class="sr">//</span> <span class="err">如果启用这个模式，缓存策略将会更改成</span> <span class="no">NSURLCache</span>
    <span class="no">SDWebImageDownloaderUseNSURLCache</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">如果图片是从</span> <span class="no">NSURLCache</span> <span class="err">中读取到的，则使用</span> <span class="kp">nil</span> <span class="err">来作为回调</span> <span class="n">block</span> <span class="err">的传入参数</span>
    <span class="sr">//</span> <span class="err">常常会与</span> <span class="no">SDWebImageDownloaderUseNSURLCache</span> <span class="err">组合使用</span>
    <span class="no">SDWebImageDownloaderIgnoreCachedResponse</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">3</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">当设备为</span> <span class="n">iOS</span> <span class="mi">4</span> <span class="err">以上的情况，则在后台可以继续下载图片</span>
    <span class="sr">//</span> <span class="err">通过向系统额外申请时间来完成数据请求操作</span>
    <span class="sr">//</span> <span class="err">如果后台任务终止，则操作会取消</span>
    <span class="no">SDWebImageDownloaderContinueInBackground</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">4</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">设置</span> <span class="no">NSMutableURLRequest</span><span class="o">.</span><span class="n">HTTPShouldHandleCookies</span> <span class="o">=</span> <span class="no">YES</span>
    <span class="sr">//</span> <span class="err">从而处理存储在</span> <span class="no">NSHTTPCookieStore</span> <span class="err">的</span> <span class="n">cookie</span>
    <span class="no">SDWebImageDownloaderHandleCookies</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">5</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">允许使用不受信的</span> <span class="no">SSL</span> <span class="err">证书</span>
    <span class="sr">//</span> <span class="err">主要用于测试</span>
    <span class="sr">//</span> <span class="err">常用在开发环境下</span>
    <span class="no">SDWebImageDownloaderAllowInvalidSSLCertificates</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">6</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">图片放在优先级更高的队列中</span>
    <span class="no">SDWebImageDownloaderHighPriority</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">7</span><span class="p">,</span>
<span class="p">};</span></code></pre></div>
<p>另外，对于下载顺序，SD 也为我们提供了两种不同的下载顺序枚举：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">typedef</span> <span class="no">NS_ENUM</span><span class="p">(</span><span class="no">NSInteger</span><span class="p">,</span> <span class="no">SDWebImageDownloaderExecutionOrder</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">先进先出</span> <span class="err">默认操作顺序</span>
    <span class="no">SDWebImageDownloaderFIFOExecutionOrder</span><span class="p">,</span>
    <span class="sr">//</span> <span class="err">后进先出</span>
    <span class="no">SDWebImageDownloaderLIFOExecutionOrder</span>
<span class="p">};</span></code></pre></div>
<p>options 枚举已经几乎将所有的开发场景所需要的模式考虑进来。下面我们来看一看具体的实现代码。</p>
<h2 id="downloader--1">Downloader 的私有成员对象</h2>
<p>先来看下 Class 的 property 对象的作用：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@interface</span> <span class="no">SDWebImageDownloader</span> <span class="p">()</span>
<span class="sr">//</span> <span class="no">NSOperation</span> <span class="err">操作队列</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">strong</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">NSOperationQueue</span> <span class="o">*</span><span class="n">downloadQueue</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">最后添加的</span> <span class="no">Operation</span> <span class="err">，顺序为后进先出顺序</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">weak</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">NSOperation</span> <span class="o">*</span><span class="n">lastAddedOperation</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">图片下载类</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">assign</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">Class</span> <span class="n">operationClass</span><span class="p">;</span>
<span class="sr">//</span> <span class="no">URL</span> <span class="err">回调字典</span>
<span class="sr">//</span> <span class="n">key</span> <span class="err">是图片的</span> <span class="no">URL</span>
<span class="sr">//</span> <span class="n">value</span> <span class="err">是一个数组，包含每个图片的回调信息</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">strong</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">NSMutableDictionary</span> <span class="o">*</span><span class="no">URLCallbacks</span><span class="p">;</span>
<span class="sr">//</span> <span class="no">HTTP</span> <span class="err">头信息</span>
<span class="vi">@property</span> <span class="p">(</span><span class="n">strong</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="no">NSMutableDictionary</span> <span class="o">*</span><span class="no">HTTPHeaders</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">并行的处理所有下载操作的网络响应</span>
<span class="sr">//</span> <span class="err">实现网络序列化的实例</span>
<span class="sr">//</span> <span class="err">对于</span> <span class="no">URLCallbacks</span> <span class="err">的所有修改，都需要放在</span> <span class="n">barrierQueue</span> <span class="err">中，并通过</span> <span class="n">dispatch_barrier_sync</span> <span class="err">形式</span>
<span class="sr">//</span> <span class="err">用于保证线程安全性</span>
<span class="vi">@property</span> <span class="p">(</span><span class="no">SDDispatchQueueSetterSementics</span><span class="p">,</span> <span class="n">nonatomic</span><span class="p">)</span> <span class="n">dispatch_queue_t</span> <span class="n">barrierQueue</span><span class="p">;</span>
<span class="vi">@end</span></code></pre></div>
<p>由于需要保证多个图片可以同时下载，为了保证 <code>URLCallbacks</code> 的线程安全，我们使用 GCD 中的 <code>dispatch_barrier_sync</code> 为进程设置<strong>栅栏(barrier)</strong>，它会等待所有位于栅栏函数之前的操作执行完成后再执行，并且在栅栏函数执行完成后，其后续操作才会开始执行，这个函数需要同 <code>dispatch_queue_create</code> 生成的 Dispatch 的<strong>同步队列</strong>(Concurrent Dispatch Queue)共同使用。</p>
<p>有了这些对于类成员的认识，开始阅读 Downloader 的源码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">/**</span>
<span class="sr"> *  下载操作</span>
<span class="sr"> *</span>
<span class="sr"> *  @param url            下载 URL</span>
<span class="sr"> *  @param options        下载操作选项</span>
<span class="sr"> *  @param progressBlock  过程 block</span>
<span class="sr"> *  @param completedBlock 完成 block</span>
<span class="sr"> *</span>
<span class="sr"> *  @return 遵循 SDWebImageOperation 协议的对象</span>
<span class="sr"> */</span>
 <span class="o">-</span> <span class="p">(</span><span class="nb">id</span> <span class="o">&lt;</span><span class="no">SDWebImageOperation</span><span class="o">&gt;</span><span class="p">)</span><span class="ss">downloadImageWithURL</span><span class="p">:(</span><span class="no">NSURL</span> <span class="o">*</span><span class="p">)</span><span class="n">url</span> <span class="ss">options</span><span class="p">:(</span><span class="no">SDWebImageDownloaderOptions</span><span class="p">)</span><span class="n">options</span> <span class="ss">progress</span><span class="p">:(</span><span class="no">SDWebImageDownloaderProgressBlock</span><span class="p">)</span><span class="n">progressBlock</span> <span class="ss">completed</span><span class="p">:(</span><span class="no">SDWebImageDownloaderCompletedBlock</span><span class="p">)</span><span class="n">completedBlock</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">定义下载</span> <span class="n">operation</span>
    <span class="n">__block</span> <span class="no">SDWebImageDownloaderOperation</span> <span class="o">*</span><span class="n">operation</span><span class="p">;</span>
    <span class="sr">//</span> <span class="n">weakly</span> <span class="nb">self</span> <span class="err">解除引用环</span>
    <span class="n">__weak</span> <span class="n">__typeof</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span><span class="n">wself</span> <span class="o">=</span> <span class="nb">self</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">添加回调闭包，传入</span><span class="no">URL</span><span class="err">、过程</span> <span class="n">block</span><span class="err">、完成</span> <span class="n">block</span>
    <span class="o">[</span><span class="nb">self</span> <span class="ss">addProgressCallback</span><span class="p">:</span><span class="n">progressBlock</span> <span class="ss">completedBlock</span><span class="p">:</span><span class="n">completedBlock</span> <span class="ss">forURL</span><span class="p">:</span><span class="n">url</span> <span class="ss">createCallback</span><span class="p">:</span><span class="o">^</span><span class="p">{</span>
        <span class="sr">//</span> <span class="err">设置下载时限，默认为</span> <span class="mi">15</span> <span class="err">秒</span>
        <span class="no">NSTimeInterval</span> <span class="n">timeoutInterval</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">downloadTimeout</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">timeoutInterval</span> <span class="o">==</span> <span class="mi">0</span><span class="o">.</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">timeoutInterval</span> <span class="o">=</span> <span class="mi">15</span><span class="o">.</span><span class="mi">0</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">创建</span> <span class="no">HTTP</span> <span class="err">请求，并根据下载模式枚举设置相关属性</span>
        <span class="sr">//</span> <span class="err">为了防止有可能出现的重复缓存问题，如果没有显式声明需要缓存管理，则不启用图片请求的缓存操作</span>
        <span class="no">NSMutableURLRequest</span> <span class="o">*</span><span class="n">request</span> <span class="o">=</span> <span class="o">[[</span><span class="no">NSMutableURLRequest</span> <span class="n">alloc</span><span class="o">]</span> <span class="ss">initWithURL</span><span class="p">:</span><span class="n">url</span> <span class="ss">cachePolicy</span><span class="p">:(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageDownloaderUseNSURLCache</span> <span class="p">?</span> <span class="no">NSURLRequestUseProtocolCachePolicy</span> <span class="p">:</span> <span class="no">NSURLRequestReloadIgnoringLocalCacheData</span><span class="p">)</span> <span class="ss">timeoutInterval</span><span class="p">:</span><span class="n">timeoutInterval</span><span class="o">]</span><span class="p">;</span>
       	<span class="sr">//</span> <span class="err">是否处理</span> <span class="n">cookie</span>
        <span class="n">request</span><span class="o">.</span><span class="n">HTTPShouldHandleCookies</span> <span class="o">=</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageDownloaderHandleCookies</span><span class="p">);</span>
        <span class="sr">//</span> <span class="err">是否需要传输数据</span>
        <span class="sr">//</span> <span class="err">返回在接到上一个请求的响应之前，是否需要传输数据</span>
        <span class="n">request</span><span class="o">.</span><span class="n">HTTPShouldUsePipelining</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">设置请求头，需要根据需要过滤指定</span> <span class="no">URL</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">wself</span><span class="o">.</span><span class="n">headersFilter</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">request</span><span class="o">.</span><span class="n">allHTTPHeaderFields</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">headersFilter</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="o">[</span><span class="n">wself</span><span class="o">.</span><span class="n">HTTPHeaders</span> <span class="n">copy</span><span class="o">]</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">else</span> <span class="p">{</span>
            <span class="n">request</span><span class="o">.</span><span class="n">allHTTPHeaderFields</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">HTTPHeaders</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">创建下载</span> <span class="n">operation</span>
        <span class="n">operation</span> <span class="o">=</span> <span class="o">[[</span><span class="n">wself</span><span class="o">.</span><span class="n">operationClass</span> <span class="n">alloc</span><span class="o">]</span> <span class="ss">initWithRequest</span><span class="p">:</span><span class="n">request</span>
                                                        <span class="ss">inSession</span><span class="p">:</span><span class="nb">self</span><span class="o">.</span><span class="n">session</span>
                                                          <span class="ss">options</span><span class="p">:</span><span class="n">options</span>
                                                         <span class="ss">progress</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="no">NSInteger</span> <span class="n">receivedSize</span><span class="p">,</span> <span class="no">NSInteger</span> <span class="n">expectedSize</span><span class="p">)</span> <span class="p">{</span>
                                                             <span class="sr">//</span> <span class="n">strongly</span> <span class="nb">self</span><span class="err">，保证生命周期</span>
                                                             <span class="no">SDWebImageDownloader</span> <span class="o">*</span><span class="n">sself</span> <span class="o">=</span> <span class="n">wself</span><span class="p">;</span>
                                                             <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sself</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
                                                             <span class="sr">//</span> <span class="no">URL</span> <span class="err">回调数组，以</span> <span class="no">URL</span> <span class="err">为</span> <span class="n">key</span> <span class="err">存储回调</span> <span class="n">callback</span>
                                                             <span class="n">__block</span> <span class="no">NSArray</span> <span class="o">*</span><span class="n">callbacksForURL</span><span class="p">;</span>
                                                             <span class="n">dispatch_sync</span><span class="p">(</span><span class="n">sself</span><span class="o">.</span><span class="n">barrierQueue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
                                                                 <span class="sr">//</span> <span class="err">从全局字典中获取指定的</span> <span class="n">callback</span>
                                                                 <span class="n">callbacksForURL</span> <span class="o">=</span> <span class="o">[</span><span class="n">sself</span><span class="o">.</span><span class="n">URLCallbacks</span><span class="o">[</span><span class="n">url</span><span class="o">]</span> <span class="n">copy</span><span class="o">]</span><span class="p">;</span>
                                                             <span class="p">});</span>
                                                             <span class="k">for</span> <span class="p">(</span><span class="no">NSDictionary</span> <span class="o">*</span><span class="n">callbacks</span> <span class="k">in</span> <span class="n">callbacksForURL</span><span class="p">)</span> <span class="p">{</span>
                                                                 <span class="sr">//</span> <span class="err">执行运行时指定图片的回调</span> <span class="n">block</span>
                                                                 <span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
                                                                     <span class="no">SDWebImageDownloaderProgressBlock</span> <span class="n">callback</span> <span class="o">=</span> <span class="n">callbacks</span><span class="o">[</span><span class="n">kProgressCallbackKey</span><span class="o">]</span><span class="p">;</span>
                                                                     <span class="k">if</span> <span class="p">(</span><span class="n">callback</span><span class="p">)</span> <span class="n">callback</span><span class="p">(</span><span class="n">receivedSize</span><span class="p">,</span> <span class="n">expectedSize</span><span class="p">);</span>
                                                                 <span class="p">});</span>
                                                             <span class="p">}</span>
                                                         <span class="p">}</span>
                                                        <span class="ss">completed</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="no">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="no">NSData</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="no">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">,</span> <span class="no">BOOL</span> <span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
                                                            <span class="sr">//</span> <span class="n">strongly</span> <span class="nb">self</span><span class="p">,</span> <span class="err">保证生命周期</span>
                                                            <span class="no">SDWebImageDownloader</span> <span class="o">*</span><span class="n">sself</span> <span class="o">=</span> <span class="n">wself</span><span class="p">;</span>
                                                            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sself</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
                                                            <span class="sr">//</span> <span class="err">完成时</span> <span class="n">callback</span> <span class="err">取方法与上方相同</span>
                                                            <span class="sr">//</span> <span class="err">因为是使用字典进行管理</span>
                                                            <span class="n">__block</span> <span class="no">NSArray</span> <span class="o">*</span><span class="n">callbacksForURL</span><span class="p">;</span>
                                                            <span class="sr">//</span> <span class="err">需要注意的是，这里使用了栅栏函数解决了选择竞争问题</span>
                                                            <span class="n">dispatch_barrier_sync</span><span class="p">(</span><span class="n">sself</span><span class="o">.</span><span class="n">barrierQueue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
                                                                <span class="n">callbacksForURL</span> <span class="o">=</span> <span class="o">[</span><span class="n">sself</span><span class="o">.</span><span class="n">URLCallbacks</span><span class="o">[</span><span class="n">url</span><span class="o">]</span> <span class="n">copy</span><span class="o">]</span><span class="p">;</span>
                                                                <span class="k">if</span> <span class="p">(</span><span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
                                                                    <span class="o">[</span><span class="n">sself</span><span class="o">.</span><span class="n">URLCallbacks</span> <span class="ss">removeObjectForKey</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
                                                                <span class="p">}</span>
                                                            <span class="p">});</span>
                                                            <span class="k">for</span> <span class="p">(</span><span class="no">NSDictionary</span> <span class="o">*</span><span class="n">callbacks</span> <span class="k">in</span> <span class="n">callbacksForURL</span><span class="p">)</span> <span class="p">{</span>
                                                                <span class="no">SDWebImageDownloaderCompletedBlock</span> <span class="n">callback</span> <span class="o">=</span> <span class="n">callbacks</span><span class="o">[</span><span class="n">kCompletedCallbackKey</span><span class="o">]</span><span class="p">;</span>
                                                                <span class="k">if</span> <span class="p">(</span><span class="n">callback</span><span class="p">)</span> <span class="n">callback</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">error</span><span class="p">,</span> <span class="n">finished</span><span class="p">);</span>
                                                            <span class="p">}</span>
                                                        <span class="p">}</span>
                                                        <span class="ss">cancelled</span><span class="p">:</span><span class="o">^</span><span class="p">{</span>
                                                            <span class="sr">//</span> <span class="n">strongly</span> <span class="nb">self</span><span class="err">，保证生命周期</span>
                                                            <span class="no">SDWebImageDownloader</span> <span class="o">*</span><span class="n">sself</span> <span class="o">=</span> <span class="n">wself</span><span class="p">;</span>
                                                            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sself</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
                                                            <span class="sr">//</span> <span class="err">与前方的执行操作进行栅栏隔离操作</span>
                                                            <span class="sr">//</span> <span class="err">保证在删除的时候没有执行自定对于</span> <span class="n">callback</span> <span class="err">的读写操作</span>
                                                            <span class="n">dispatch_barrier_async</span><span class="p">(</span><span class="n">sself</span><span class="o">.</span><span class="n">barrierQueue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
                                                                <span class="o">[</span><span class="n">sself</span><span class="o">.</span><span class="n">URLCallbacks</span> <span class="ss">removeObjectForKey</span><span class="p">:</span><span class="n">url</span><span class="o">]</span><span class="p">;</span>
                                                            <span class="p">});</span>
                                                        <span class="p">}</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">是否需要对图片进行压缩处理</span>
        <span class="n">operation</span><span class="o">.</span><span class="n">shouldDecompressImages</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">shouldDecompressImages</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">认证请求操作，后面详细分析</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">wself</span><span class="o">.</span><span class="n">urlCredential</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">operation</span><span class="o">.</span><span class="n">credential</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">urlCredential</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">wself</span><span class="o">.</span><span class="n">username</span> <span class="o">&amp;&amp;</span> <span class="n">wself</span><span class="o">.</span><span class="n">password</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">operation</span><span class="o">.</span><span class="n">credential</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLCredential</span> <span class="ss">credentialWithUser</span><span class="p">:</span><span class="n">wself</span><span class="o">.</span><span class="n">username</span> <span class="ss">password</span><span class="p">:</span><span class="n">wself</span><span class="o">.</span><span class="n">password</span> <span class="ss">persistence</span><span class="p">:</span><span class="no">NSURLCredentialPersistenceForSession</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">设置下载操作的优先级操作，需要根据下载模式枚举来判断</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageDownloaderHighPriority</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">operation</span><span class="o">.</span><span class="n">queuePriority</span> <span class="o">=</span> <span class="no">NSOperationQueuePriorityHigh</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageDownloaderLowPriority</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">operation</span><span class="o">.</span><span class="n">queuePriority</span> <span class="o">=</span> <span class="no">NSOperationQueuePriorityLow</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">向下载操作的队列中增加当前操作</span>
        <span class="o">[</span><span class="n">wself</span><span class="o">.</span><span class="n">downloadQueue</span> <span class="ss">addOperation</span><span class="p">:</span><span class="n">operation</span><span class="o">]</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">wself</span><span class="o">.</span><span class="n">executionOrder</span> <span class="o">==</span> <span class="no">SDWebImageDownloaderLIFOExecutionOrder</span><span class="p">)</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">如果执行顺序为后进先出的栈结构</span>
            <span class="sr">//</span> <span class="err">则将新添加的</span> <span class="n">operation</span> <span class="err">作为当前最后一个</span> <span class="n">operation</span> <span class="err">的依赖，按照顺序逐个执行</span>
            <span class="o">[</span><span class="n">wself</span><span class="o">.</span><span class="n">lastAddedOperation</span> <span class="ss">addDependency</span><span class="p">:</span><span class="n">operation</span><span class="o">]</span><span class="p">;</span>
            <span class="n">wself</span><span class="o">.</span><span class="n">lastAddedOperation</span> <span class="o">=</span> <span class="n">operation</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span><span class="o">]</span><span class="p">;</span></code></pre></div>
<p>整个流程已经了解，下面分析一些细小的细节问题：</p>
<h2 id="url--block-">全局字典，将 URL 与回调 block 的映射容器</h2>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">__block</span> <span class="no">NSArray</span> <span class="o">*</span><span class="n">callbacksForURL</span><span class="p">;</span>
<span class="n">dispatch_sync</span><span class="p">(</span><span class="n">sself</span><span class="o">.</span><span class="n">barrierQueue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="sr">//</span> <span class="n">dispatch_barrier_sync</span> <span class="p">(</span><span class="n">sself</span><span class="o">.</span><span class="n">barrierQueue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
	<span class="n">callbacksForURL</span> <span class="o">=</span> <span class="o">[</span><span class="n">sself</span><span class="o">.</span><span class="n">URLCallbacks</span><span class="o">[</span><span class="n">url</span><span class="o">]</span> <span class="n">copy</span><span class="o">]</span><span class="p">;</span>
<span class="p">});</span>
<span class="k">for</span> <span class="p">(</span><span class="no">NSDictionary</span> <span class="o">*</span><span class="n">callbacks</span> <span class="k">in</span> <span class="n">callbacksForURL</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
		<span class="no">SDWebImageDownloaderProgressBlock</span> <span class="n">callback</span> <span class="o">=</span> <span class="n">callbacks</span><span class="o">[</span><span class="n">kProgressCallbackKey</span><span class="o">]</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">callback</span><span class="p">)</span> <span class="n">callback</span><span class="p">(</span><span class="n">receivedSize</span><span class="p">,</span> <span class="n">expectedSize</span><span class="p">);</span>
	<span class="p">});</span>
<span class="p">}</span></code></pre></div>
<p>在执行进行中 block、完成 block 的时候，都会使用以上这几行代码。其作用是为了维护一个字典，key 为图片的唯一标识 url ，值为一个 block 的数组，来统一管理这些回调方法。其大致的结构图如下表示：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/URLCallBacks.png" alt="URLCallBacks" /><br />
(图片来源：<a href="http://www.cnblogs.com/polobymulberry/p/5017995.html#_label4">polobymulberry</a>)</p>
<p>执行过程中的 block 的时候，在初始化字典管理的时候使用了 <code>dispatch_sync</code> 同步执行操作，而没有增加栅栏函数（注释中为增加栅栏函数）。但在对于完成 block 的管理时，为了保证线程安全的竞争选择问题，SD 作者选用了栅栏函数对线程进行了先后执行的规定。为什么这里不用栅栏呢？笔者的理解如下：<strong>由于这两个位置，都是对于 <code>URLCallbacks</code> 的读写操作，而在这之前是没有任何更新 <code>URLCallbacks</code> 的操作，所以不需要设置栅栏，只需要同步继续即可。而对于栅栏函数，是用在异步操作中对于操作顺序进行控制，由于 SD 需要支持多图片同时下载，所以需要在每次的 <code>URLCallbacks</code> 写数据结束后，再进行读操作。</strong></p>
<h2 id="nsmutableurlrequest-">NSMutableURLRequest 网络请求</h2>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">NSMutableURLRequest</span> <span class="o">*</span><span class="n">request</span> <span class="o">=</span> <span class="o">[[</span><span class="no">NSMutableURLRequest</span> <span class="n">alloc</span><span class="o">]</span> <span class="ss">initWithURL</span><span class="p">:</span><span class="n">url</span>
                                                                    <span class="ss">cachePolicy</span><span class="p">:(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageDownloaderUseNSURLCache</span> <span class="p">?</span> <span class="no">NSURLRequestUseProtocolCachePolicy</span> <span class="p">:</span> <span class="no">NSURLRequestReloadIgnoringLocalCacheData</span><span class="p">)</span>
                                                                <span class="ss">timeoutInterval</span><span class="p">:</span><span class="n">timeoutInterval</span><span class="o">]</span><span class="p">;</span></code></pre></div>
<p><code>initWithURL</code> 的作用是根据 url 、缓存策略（Cache Policy）、下载最大时限（Time Out Interval）来产生一个 <code>NSURLRequest</code>。先来看下缓存策略的选择：</p>
<ul>
  <li><strong>SDWebImageDownloaderUseNSURLCache</strong>：在 SDWebImage 中，默认条件下，请求是不使用 <code>NSURLCache</code> 的。如果使用该选项，<code>NSURLCache</code> 就应该使用默认的缓存策略 <code>NSURLRequestUseProtocolCachePolicy</code>。</li>
  <li><strong>NSURLRequestUseProtocolCachePolicy</strong>：对特定 url 请求使用网络协议（例如 HTTP）中实现的缓存逻辑。这是一个默认的策略。该策略表示如果缓存不存在，直接从服务端获取。如果缓存存在，会根据 Response 中的 Cache-Control 字段判断下一步操作。例如：当 Cache-Control 字段为 must-revalidata ，则会询问服务端该数据是否有更新，无更新则返回给用户缓存数据，若已经更新，则请求服务器以获取最新数据。</li>
  <li><strong>NSURLRequestReloadIgnoringLocalCacheData</strong>：数据需要从原始地址（一般就是重新从服务器获取）。不使用现有缓存。</li>
</ul>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="err">要点一</span>
<span class="n">request</span><span class="o">.</span><span class="n">HTTPShouldHandleCookies</span> <span class="o">=</span> <span class="p">(</span><span class="n">options</span> <span class="o">&amp;</span> <span class="no">SDWebImageDownloaderHandleCookies</span><span class="p">);</span>
<span class="sr">//</span> <span class="err">要点二</span>
<span class="n">request</span><span class="o">.</span><span class="n">HTTPShouldUsePipelining</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
<span class="sr">//</span> <span class="err">要点三</span>
<span class="k">if</span> <span class="p">(</span><span class="n">wself</span><span class="o">.</span><span class="n">headersFilter</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">request</span><span class="o">.</span><span class="n">allHTTPHeaderFields</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">headersFilter</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="o">[</span><span class="n">wself</span><span class="o">.</span><span class="n">HTTPHeaders</span> <span class="n">copy</span><span class="o">]</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
	<span class="n">request</span><span class="o">.</span><span class="n">allHTTPHeaderFields</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">HTTPHeaders</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>后面就是对于 request 的一些属性的设置，从属性名上可以看出使用的是 HTTP 协议：</p>
<ul>
  <li>要点一：<code>HTTPShouldHandleCookies</code> 如果设置为 YES，在处理时我们直接查询 <code>NSHTTPCookieStore</code> 中的 cookies 即可。<code>HTTPShouldHandleCookies</code> 这个策略表示是否应该给 Request 设置 cookie 并伴随着 Request 一起发送出去。然后 Response 返回的 cookie 会继续根据访问策略（Cookie Acceptance Policy）接收到系统中。</li>
  <li>要点二：<code>HTTPShouldUsePipelining</code> 表示 receiver （常常理解为 client 客户端）的下一个信息是否必须等到上一个请求回复才能发送。如果为 YES 表示可以， NO 反之。这个就是我们常常提到的 <strong>HTTP 管线化</strong>（HTTP Pipelining），如此可以显著降低请求的加载时间。</li>
  <li>要点三：<code>headersFilter</code> 是使用自定义方法来设置 HTTP 的 Head Filed。这里可以看下 HTTPHeader 的初始化（下载 webp 图片与通常情况下的 header 不同）：</li>
</ul>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#ifdef SD_WEBP</span>
        <span class="n">_HTTPHeaders</span> <span class="o">=</span> <span class="o">[</span><span class="err">@</span><span class="p">{</span><span class="err">@</span><span class="s2">&quot;Accept&quot;</span><span class="p">:</span> <span class="err">@</span><span class="s2">&quot;image/webp,image/*;q=0.8&quot;</span><span class="p">}</span> <span class="n">mutableCopy</span><span class="o">]</span><span class="p">;</span>
<span class="c1">#else</span>
        <span class="n">_HTTPHeaders</span> <span class="o">=</span> <span class="o">[</span><span class="err">@</span><span class="p">{</span><span class="err">@</span><span class="s2">&quot;Accept&quot;</span><span class="p">:</span> <span class="err">@</span><span class="s2">&quot;image/*;q=0.8&quot;</span><span class="p">}</span> <span class="n">mutableCopy</span><span class="o">]</span><span class="p">;</span>
<span class="c1">#endif</span></code></pre></div>
<h2 id="nsurlcredential-">NSURLCredential 身份认证</h2>
<blockquote>
  <p>web 服务可以在返回 HTTP 响应时附带认证要求的 Challenge，作用是询问 HTTP 请求的发起方是谁，这时候发起方应提供正确的用户名和密码（认证信息），然后 web 服务才会返回真正的 HTTP 响应。</p>
</blockquote>
<blockquote>
  <p>收到认证要求时，<code>NSURLConnection</code> 的委托对象会收到相应的消息并得到一个 <code>NSURLAuthenticationChallenge</code> 实例。该实例的发送方遵守 <code>NSURLAuthenticationChallengeSender</code> 协议。为了继续收到真实的数据，需要向该发送方向发回一个 <code>NSURLCredential</code> 实例。</p>
</blockquote>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="p">(</span><span class="n">wself</span><span class="o">.</span><span class="n">urlCredential</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">operation</span><span class="o">.</span><span class="n">credential</span> <span class="o">=</span> <span class="n">wself</span><span class="o">.</span><span class="n">urlCredential</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">wself</span><span class="o">.</span><span class="n">username</span> <span class="o">&amp;&amp;</span> <span class="n">wself</span><span class="o">.</span><span class="n">password</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">operation</span><span class="o">.</span><span class="n">credential</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSURLCredential</span> <span class="ss">credentialWithUser</span><span class="p">:</span><span class="n">wself</span><span class="o">.</span><span class="n">username</span>
                                                    <span class="ss">password</span><span class="p">:</span><span class="n">wself</span><span class="o">.</span><span class="n">password</span>
                                                 <span class="ss">persistence</span><span class="p">:</span><span class="no">NSURLCredentialPersistenceForSession</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>当已经有用 <code>NSURLCredential</code> ，则直接使用，没有的话则重新构建一个实例并存储下来。<code>NSURLCredential</code> 在其中的作用就是缓存对于证书的授权处理。这是对于 https 协议而言，如果想了解更多建议阅读 <a href="https://developer.apple.com/reference/foundation/nsurlcredential">Foundation的官方文档</a>。</p>
<h2 id="section">总结</h2>
<p>在 Downloader 中，主要的操作就是用于组织一个 <code>URLCallbacks</code> 字典，用于管理图片指定的进行 block 、完成 block。并且，在 <code>downloadImageWithURL:</code> 方法中，Downloader 其实一直在更新一个 operation 并作为返回值。所以，Downloader 的主要作用是实现多图片异步下载请求，并将其封装为一个 operation 提交给上层统一管理。</p>
<p>下一篇主要讲解一下 DownloaderOperation 下载操作任务管理。</p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《SDWebImage Source Probe: Downloader》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/SDWebImage3/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/SDWebImage3/&amp;title=《SDWebImage Source Probe: Downloader》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/SDWebImage3/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/SDWebImage3/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>